///////////////////////////////////////////////////////////////////////////

#ifndef _AUDIOTRACK_H_
#include "audiotrack.h"
#endif

///////////////////////////////////////////////////////////////////////////

#include <numeric>
#include <cassert>

#include "timer.h"

///////////////////////////////////////////////////////////////////////////


AudioTrack::AudioTrack()
: m_EnegyHistory(HISTORY_SIZE),
m_Sys( portaudio::System::instance() )
{
}


///////////////////////////////////////////////////////////////////////////


void AudioTrack::FFT()
{
    // Convert amplitudes to complex
    m_Spectrum.resize( m_LeftChannel.size() );
    for (size_t i = 0; i < m_Spectrum.size(); ++i)
    {
        m_Spectrum[i] = complex( m_LeftChannel[i], m_RightChannel[i] );
    }

    // FFT
    CFFT::Forward( &m_Spectrum[0], m_Spectrum.size() );
}


///////////////////////////////////////////////////////////////////////////


bool AudioTrack::DetectBeat()
{
    bool beatDetected = false;
    ComputeInstantEnergy();

    if (m_EnegyHistory.size() == HISTORY_SIZE)
    {
        // Detect beat
        const double C = 1.3;
        beatDetected = m_InstantEnergy > GetAverageEnergy() * C;
    }

    // Save instant energy into history
    m_EnegyHistory.push_back(m_InstantEnergy);

    return beatDetected;
}


///////////////////////////////////////////////////////////////////////////


bool AudioTrack::DetectBeatFreq()
{
	m_FreqAmplitudes.resize( m_Spectrum.size() );
	for ( size_t i = 0; i < m_Spectrum.size(); i++ )
		m_FreqAmplitudes[i] = m_Spectrum[i].norm();

	FreqAmplitudes subbands;
	subbands.resize(SUBBANDS_SIZE, 0);
	for ( size_t i = 0; i < SUBBANDS_SIZE; i++ )
		subbands[i] = 32/1024 * std::accumulate( m_FreqAmplitudes.begin() + i*32, m_FreqAmplitudes.begin() + (i+1)*32, 0.0 );
	
    const double C = 1.3;
    bool beatDetected = subbands.back() > GetAverageEnergy() * C;

    m_EnegyHistory.push_back(subbands.back());

	return beatDetected;
}


///////////////////////////////////////////////////////////////////////////


void AudioTrack::ComputeInstantEnergy()
{
    double leftChannelEnergy = GetChannelEnergy(m_LeftChannel);
    double rightChannelEnergy = GetChannelEnergy(m_RightChannel);

    m_InstantEnergy = leftChannelEnergy + rightChannelEnergy;
}


///////////////////////////////////////////////////////////////////////////


double AudioTrack::GetChannelEnergy( const Amplitudes& _channel ) const
{
    double result = 0;
    for (size_t i = 0; i < _channel.size(); ++i)
    {
        double amplitude = _channel[i];
        result += amplitude * amplitude;
    }

    return result;
}


///////////////////////////////////////////////////////////////////////////


double AudioTrack::GetAverageEnergy() const
{
    return std::accumulate(m_EnegyHistory.begin(),
                           m_EnegyHistory.end(), 
                           0.0 )                   / m_EnegyHistory.size();
}


///////////////////////////////////////////////////////////////////////////


double AudioTrack::ComputeVariance( double _averageEnergy ) const
{
    double variance = 0;
    for (size_t i = 0; i < HISTORY_SIZE; ++i)
    {
        double diff = m_EnegyHistory[i] - _averageEnergy;
        variance += diff * diff;
    }
    return variance / HISTORY_SIZE;
}


///////////////////////////////////////////////////////////////////////////


void AudioTrack::Play()
{
    // Set up the parameters required to open a (Callback)Stream:
    portaudio::DirectionSpecificStreamParameters outParams( m_Sys.defaultOutputDevice(),
                                                            2,
                                                            portaudio::INT16,
                                                            false,
                                                            m_Sys.defaultOutputDevice().defaultLowOutputLatency(),
                                                            NULL );

    portaudio::StreamParameters params( portaudio::DirectionSpecificStreamParameters::null(),
                                        outParams,
                                        m_Rate,
                                        m_LeftChannel.size(),
                                        paClipOff );

    // Create (and open) a new Stream, using the AudioTrack::PlayCallback function as a callback:
    portaudio::MemFunCallbackStream<AudioTrack> stream(params, *this, &AudioTrack::PlayCallback);

    // Start the Stream (audio playback starts):
    stream.start();

    Timer::Sleep(10000);
}


///////////////////////////////////////////////////////////////////////////


int AudioTrack::PlayCallback( const void *inputBuffer,
                              void *outputBuffer,
                              unsigned long framesPerBuffer,
                              const PaStreamCallbackTimeInfo *timeInfo,
                              PaStreamCallbackFlags statusFlags )
{
    assert(outputBuffer != NULL);

    short** out = static_cast<short**>(outputBuffer);

    for (size_t i = 0; i < framesPerBuffer; ++i)
    {
        out[0][i] = m_LeftChannel[i];
        out[1][i] = m_RightChannel[i];
    }

    Read();

    return paContinue;
}


///////////////////////////////////////////////////////////////////////////
